Skip to main content

VB365 SharePoint Auto-Protect

Add-UnprotectedSitesToJob.ps1 finds SharePoint Online sites in a Veeam Backup for Microsoft 365 (VB365) organization that are not in any backup job, adds them to a chosen backup job, and reports what it did. It is built to run unattended as a Windows scheduled task, so new SharePoint sites get protected without anyone having to remember to add them.


What it does​

Each run:

  1. Finds candidate sites. Asks VB365 for every site it reports as "not in a job" (Get-VBOOrganizationSite -NotInJob).
  2. Double-checks every job. Reads the selected and excluded items of every job in the organization. A site is skipped if it is:
    • already selected in a job, or
    • explicitly excluded in a job (someone excluded it on purpose, so the script leaves it alone).
  3. Applies your skip patterns. Skips any site whose URL matches -ExcludeUrlPattern (for example, the app catalog or test sites).
  4. Skips deleted sites (optional). If Graph is configured, checks the list of sites that still exist in Microsoft 365 and skips any that have been deleted. If this check fails, the run stops rather than risk adding deleted sites.
  5. Adds the remaining sites to the target job (up to -MaxSitesToAdd, if set).
  6. Reports. Writes a log file and a CSV report, and a summary to the Windows Application event log.

Nothing is ever removed from a job. The script only adds.


Files in this folder​

FilePurpose
Add-UnprotectedSitesToJob.ps1The script
README.mdThis file
graph.cred(Optional) Encrypted Graph app credential, created during setup
Logs\Created automatically. Holds a .log and .csv for each run

Requirements​

  • Run on the VB365 server (or anywhere the VB365 PowerShell module is installed; use -VBOServer to connect to a remote server).
  • Windows PowerShell 5.1 (powershell.exe).
  • Account permissions: the account running the script needs admin rights on the VB365 server to use the Veeam cmdlets.
  • Target job: must be a job that backs up selected items. Entire Organization jobs can't have sites added to them, and the script will refuse to use one.
  • For the deleted-site check (optional): an Entra ID (Azure AD) app registration with the Microsoft Graph application permission Sites.Read.All (admin consent granted) and a client secret.

Parameters​

ParameterRequiredDescription
-OrganizationNameYesVB365 organization name, e.g. contoso.onmicrosoft.com
-JobNameYesBackup job to add sites to
-ExcludeUrlPatternNoWildcard URL patterns to skip. Separate several with commas: "*/sites/appcatalog*,*/sites/test-*"
-IncludePersonalSiteNoAlso include OneDrive (personal) sites
-MaxSitesToAddNoSafety cap on how many sites to add per run. 0 (default) = no limit
-GraphTenantIdNoTenant ID or domain. Set together with -GraphCredentialPath to enable the deleted-site check
-GraphCredentialPathNoPath to the saved Graph credential file
-LogDirectoryNoWhere logs and reports go. Default: Logs next to the script
-LogRetentionDaysNoDelete logs/reports older than this. Default 90. 0 = keep forever
-EventLogSourceNoEvent log source name. Default VB365-SiteAutoProtect
-VBOServerNoOnly needed if not running on the VB365 server
-WhatIfNoDry run: shows and logs what would be added, changes nothing
-SetupGraphCredential—One-time setup mode for saving the Graph credential (see below)

Setup​

1. Copy the files​

Put the script in a folder on the VB365 server, for example C:\Scripts\VB365\.

2. (Optional) Save the Graph credential​

Skip this if you don't want the deleted-site check.

The credential file is encrypted with Windows DPAPI, which means only the account that created it, on the machine it was created on, can read it. So log on (or use runas) as the account the scheduled task will run under, then run:

cd C:\Scripts\VB365
.\Add-UnprotectedSitesToJob.ps1 -SetupGraphCredential -GraphCredentialPath "C:\Scripts\VB365\graph.cred"

When prompted:

  • User name: the app registration's Application (client) ID
  • Password: the client secret

3. Do a dry run​

.\Add-UnprotectedSitesToJob.ps1 -OrganizationName "contoso.onmicrosoft.com" -JobName "SharePoint Sites" `
-ExcludeUrlPattern "*/sites/appcatalog*" `
-GraphTenantId "contoso.onmicrosoft.com" -GraphCredentialPath "C:\Scripts\VB365\graph.cred" `
-WhatIf

Open the newest CSV in Logs\ and check the WouldAdd (WhatIf) rows look right. Pay attention to any warning about Entire Organization jobs: if one exists, sites listed as unprotected may actually already be backed up by it.

Version check: VB365 property names can differ between versions. If results look wrong, run Get-VBOJob | Select * and Get-VBOBackupItem -Job (Get-VBOJob -Name "SharePoint Sites") | Select * and confirm the JobBackupType and Site.URL properties exist on your build.

4. Register the scheduled task​

From an elevated PowerShell prompt (edit the values for your environment):

$scriptArgs = '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Scripts\VB365\Add-UnprotectedSitesToJob.ps1" ' +
'-OrganizationName "contoso.onmicrosoft.com" -JobName "SharePoint Sites" ' +
'-ExcludeUrlPattern "*/sites/appcatalog*" ' +
'-GraphTenantId "contoso.onmicrosoft.com" -GraphCredentialPath "C:\Scripts\VB365\graph.cred" ' +
'-LogDirectory "C:\Scripts\VB365\Logs"'

$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $scriptArgs
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At 6am
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 2) -MultipleInstances IgnoreNew -StartWhenAvailable

Register-ScheduledTask -TaskName "VB365 - Auto-protect SharePoint sites" -Action $action -Trigger $trigger `
-Settings $settings -User "DOMAIN\svc-vb365" -Password (Read-Host "Password") -RunLevel Highest

Important:

  • Use -File, not -Command. With -Command, the script's exit code is not passed back to Task Scheduler.
  • Schedule it to finish before the backup job runs, so newly added sites are picked up by that night's backup.
  • The first time it runs, the script creates the event log source, which needs admin rights. If the task account isn't an admin, create the source once by hand from an elevated prompt:
    New-EventLog -LogName Application -Source VB365-SiteAutoProtect

Exit codes​

Shown as Last Run Result in Task Scheduler.

CodeMeaningWhat to do
0Success. Sites were added, or there was nothing to addNothing
1Finished, but one or more sites failed to addCheck the Failed rows in the CSV for the error
2Fatal error. The run stoppedCheck the log for the FATAL line
3Another run was still in progress, so this one did nothingUsually harmless. If it repeats, the previous run may be stuck

Event log​

A summary of every run is written to Windows Logs > Application, source VB365-SiteAutoProtect, including the list of sites added. The event ID tells you the result, which makes monitoring alerts easy:

Event IDLevelMeaning
1000InformationSuccess
1001WarningSome sites failed
1002ErrorFatal error
1003WarningSkipped, already running

Logs and reports​

Each run creates two files in the log folder, named with the run's date and time:

  • AutoProtect_<timestamp>.log: step-by-step log of the run.
  • AutoProtect_<timestamp>.csv: one row per site with its outcome:
ActionMeaning
AddedAdded to the job
WouldAdd (WhatIf)Would have been added (dry run)
SkippedNot added; the Detail column says why
FailedTried to add but got an error; the Detail column has the error

Skip reasons you may see: Already selected in a job, Explicitly excluded in a job, Matched -ExcludeUrlPattern, Not found in Microsoft Graph (deleted?), Over -MaxSitesToAdd limit.


Troubleshooting​

SymptomLikely cause / fix
Exit 2: Organization ... not found / Backup job ... not foundCheck the names exactly match what VB365 shows
Exit 2: Entire Organization jobThe target job backs up the whole org. Pick a job that backs up selected items
Exit 2: Could not decrypt ... graph.credThe credential file was created by a different account or on a different machine. Re-run setup as the task account
Exit 2: Graph / token errorsClient secret expired, wrong tenant, or Sites.Read.All not admin-consented. Create a new secret and re-run setup
Deleted sites keep appearing / jobs show warnings for missing sitesEnable the Graph deleted-site check
Sites added that are already backed upAn Entire Organization job exists; see the warning in the log
-ExcludeUrlPattern seems ignoredPatterns are wildcards matched against the full URL. Include * at the start, e.g. */sites/test-*
Task shows 0x1 or other code but no logThe script didn't start: check the path in the task, that -File is used, and execution policy
Event log warning in the logThe event log source doesn't exist and the account can't create it. See step 4

Maintenance​

  • Client secret expiry: Graph client secrets expire. Note the expiry date and re-run the -SetupGraphCredential step with the new secret before it lapses, or the run will fail with exit code 2.
  • Changing the task account: re-create graph.cred while logged on as the new account.
  • Job size: keep an eye on how many objects end up in the target job. If it grows large, consider splitting sites across several jobs and pointing the script at a new one.